<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<link rel="stylesheet" href="../index.css">
	<link rel="stylesheet" href="../common.css">
	<title>decorator</title>
</head>
<body>
	<div class="container">
		<div class="page-content">
			<div class="content-box" id="content-box-1">
				<div class="content-title">修饰者模式</div>
				<div class="content-intr-txt">修饰者模式可以给动态地给某个对象添加一些额外地职责，而不会影响从这个类中派生地其他对象。</div>
				<div class="content-intr-txt">比如：有4种类型的自行车，为每一个类型的自行车定义一个单独的类型。现在给每中自行车按需求添加前灯，尾灯，铃铛三种配件。如果使用继承的方式来给每种自行车创建子类，则需要4*3=12个子类。如果把前灯，尾灯，铃铛这些对象动态组合到自行车子类上，则只需要3种子类。</div>
				<div class="content-intr-txt">这种给对象动态添加职责的方式称为修饰者模式。</div>
				<div class="content-intr-txt">案例一：飞机大战游戏中，随着经验值的增加，我们操作的飞机对象可以升级成为更厉害的飞机，一开始这些飞机只能发射普通的子弹，升级胡发射导弹，再升级后可以发射原子弹</div>
				<div class="content-intr-txt">代码如下：</div>
				<pre>
					// 案例一
					<code>
						var Plane = function() {}
						Plane.prototype.fire = function() {
							console.log('发射普通子弹');
						}
						// 添加两个装饰类，分别是导弹与原子弹
						var MissleDecorator = function(plane) {
							this.plane = plane;
						}
						MissleDecorator.prototype.fire = function() {
							this.plane.fire();
							console.log('发射导弹')
						}
						var AtomDecorator = function(plane) {
							this.plane = plane;
						}
						AtomDecorator.prototype.fire = function() {
							this.plane.fire();
							console.log('发射原子弹')
						}
						var plane = new Plane();
						// plane.fire(); 		// 发射普通子弹
						plane = new MissleDecorator(plane);
						// plane.fire(); 		// 发射普通子弹  发射导弹
						plane = new AtomDecorator(plane);
						// plane.fire();   	// 发射普通子弹  发射导弹 发射原子弹
					</code>
				</pre>
			</div>

			<div class="content-box">
				<div class="content-intr-txt">使用保存对象引用的方式实现修饰者模式</div>
				<div class="content-intr-txt">将对象中方法属性的引用保存在某个变量中，再重写该属性方法，然后再属性方法中调用原来方法的应用。</div>
				<div class="content-intr-txt">改造案例一，代码如下：</div>
				<pre>
					// 方案一改造：
					<code>
						var plane = {
							fire: function() {
								console.log('发射普通子弹')
							}
						}

						var missleDecorator = function() {
							console.log('发射导弹')
						}

						var atomDecorator = function() {
							console.log('发射原子弹');
						}

						var fire1 = plane.fire;
						plane.fire = function() {
							missleDecorator();
							fire1();
						}

						// plane.fire(); // 发射导弹  发射普通子弹

						var fire2 = plane.fire;
						plane.fire = function() {
							atomDecorator();
							fire2();
						}

						plane.fire();  // 发射原子弹  发射导弹  发射普通子弹
					</code>
				</pre>
			</div>

			<div class="content-box">
				<div class="content-intr-txt">装饰函数</div>
				<div class="content-intr-txt">再Javascript中，函数作为一等对象，并且经常使用到。但是在javascript中可以很方便的修改某个对象中方法与属性，却很难在不改动某个函数源代码的情况下，给函数添加一些额外的功能，因为我们很难切换函数的执行环境</div>
				<div class="content-intr-txt">修改函数的方法：<br>①：直接重写该函数<br>②：使用保存引用的方法</div>
				<pre>
					<code>
						var a = function() {
							console.log('a')
						}

						// 直接改写
						// a = function() {
						// 	console.log('a');
						// 	console.log('b');
						// }
						// a();

						// 保存引用
						var _a  = a;
						a = function() {
							_a();
							console.log('b')
						}
						a();

						window.onload = function() {
							console.log('load');
						}

						var _onload = window.onload || function() {}
						window.onload = function() {
							_onload();
							console.log('load new')
						}
					</code>
					// 保存引用的方式存在许多缺陷
					// 1：随着装饰者的添加中间维护的内容越来越多 如:_load ...
					// 2：导致函数的指向丢失，如下
					<code>
						var _getElementById = document.getElementById;
						document.getElementById = function(id) {
							console.log('get element by id ...');
							//return _getElementById(id) // 这里_getElementById函数的this执行window
							return _getElementById.call(document, id) // 修改
						}
						var box = document.getElementById('content-box-1')
						console.log(box)
					</code>
				</pre>
			</div>

			<div class="content-box">
				<div class="content-intr-txt">用AOP装饰函数</div>
				<div class="content-intr-txt">首先实现Function.prototype.before和Function.prototype.after方法</div>
				<pre>
					// 用AOP修饰函数
					<code>
						// 实现before after的函数原型的方法
						Function.prototype.before = function(beforeFn) {
							var _this = this; // this执行before的调用者  也就是函数的本生
							return function() {
								beforeFn.apply(this, arguments);  // 这里的this指向执行时的语法环境
								return _this.apply(this, arguments)// 这里的this指向执行时的语法环境
							}
						}

						Function.prototype.after = function(afterFn) {
							var _this = this; // this执行before的调用者  也就是函数的本生
							return function() {
								var ret = _this.apply(this, arguments);// 这里的this指向执行时的语法环境
								afterFn.apply(this, arguments);// 这里的this指向执行时的语法环境
								return ret;
							}
						}

						document.getElementById = document.getElementById.before(function() {
							console.log(this);
						})
						// document.getElementById为一个函数，使用before修饰函数，返回一个函数
						// 返回的函数被重新赋值给document对象的getElementById属性
						// document.getElementById.before(function() {
						//	console.log(this);
						// })
						// 此时返回函数的this指向getElementById函数的调用者document
						var btn = document.getElementById('content-box-1');
						// console.log(btn)  // 

						// 如果不愿改变原型方法可以使用函数代替
						var generatorAfter = function(fn, afterFn) {
							return function() {
								var ret = fn.apply(this, arguments);
								afterFn.apply(this, arguments);
								return ret;
							}
						}

						var generatorBefore = function(fn, beforeFn) {
							return function() {
								beforeFn.apply(this, arguments);
								return fn.apply(this, arguments);
							}
						}

						document.getElementById = generatorBefore(document.getElementById, function() {
							console.log(this)
						})
					</code>
				</pre>
				<pre>
					// 使用装饰函数after改写window.onload
					<code>
						window.onload = function initload() { console.log(1) }
						window.onload = 
							(window.onload || function() {})
								.after(function afn1() { console.log(2) })
								// 此处after为window.onload的装饰函数，返回一个包含afn1函数的代理函数 backfn1
								// backfn1 = function() {initload.apply(this, arguments); afn1.apply(this, arguments)()}
								.after(function afn2() { console.log(3) })
								// 此处after为backfn1的装饰函数，返回一个包含afn2函数的代理函数 backfn2
								// backfn2 = function() {backfn1.apply(this, arguments);afn2.apply(this, arguments)}
								.after(function afn3() { console.log(4) })
								// 此处after为backfn2的装饰函数，返回一个包含afn3函数的代理函数 backfn3
								// backfn3 = function() {backfn2.apply(this, arguments);afn3.apply(this, arguments)}
					</code>
				</pre>
			</div>

			<div class="content-box">
				<div class="content-intr-txt">数据统计上报</div>
				<div class="content-intr-txt">页面中有个登录按钮，当点击这个button会弹出登录浮层，与此同时要进行数据的上报</div>
				<span class="btn-common-1" id="login-btn" tag="login">登录</span>
				<span class="btn-common-1" id="login-btn-2" tag="AOP_login">AOP登录</span>
				<div class="content-intr-txt">普通实现方法的代码：使用回调函数的方法</div>
				<pre>
					// 数据统计的实现
					<code>
						var showLogin = function() {
							console.log('打开登录弹框');
							log(this.getAttribute('tag'));
						}

						var log = function(tag) {
							console.log('上报标签：' + tag);
						 (new Image).src = 'https://...'; // 上报统计
						}
						document.getElementById('login-btn').onclick = showLogin;
					</code>
					// 使用AOP来实现
					<code>
						Function.prototype.after = function(afterFn) {
							var _seft = this;
							return function() {
								var ret = _seft.apply(this, arguments);
								afterFn.apply(this, arguments);
								return ret
							}
						}
						var AopShowLogin = function() {
							console.log('打开登录弹框');
						}

						var AopLog = function() {
							console.log('上报标签：' + this.getAttribute('tag'));
						}

						document.getElementById('login-btn-2').onclick = AopShowLogin.after(AopLog);
					</code>
				</pre>
			</div>

			<div class="content-box">
				<div class="content-intr-txt">使用AOP动态修改参数</div>
				<div class="content-intr-txt">代码如下：</div>
				<pre>
					// 使用AOP修改函数的参数
					<code>
						Function.prototype.after = function(afterFn) {
							var _self = this;
							return function() {
								var ret = _self.apply(this, arguments);
								afterFn.apply(this, arguments);
								return ret;
							}
						}

						Function.prototype.before = function(beforeFn) {
							var _self = this;
							return function() {
								beforeFn.apply(this, arguments);
								return _self.apply(this, arguments);
							}
						}

						var fnuc = function(params) {
							console.log(params);
						};

						var fnucBe = function(params) {
							params.b = 'b'
						};

						var ffnuc = fnuc.before(fnucBe);

						ffnuc({a: '1'}); // { a: "1", b: "b" }
					</code>
					// 改变ajax的参数，添加token
					<code>
						var ajax = function(url, type, params) {
							console.log(params)
						}
						var getToken = function() {
							return 'Token'
						}
						var decoratorAjax = ajax.before(function(url, type, params) {
							params.token = getToken();
						})

						decoratorAjax('https://www.baidu.com', 'jsonp', {_n: '百度'});
					</code>
				</pre>
			</div>
		</div>
	</div>


	<script type="text/javascript">
		(function() {
			// 案例一
			/*var Plane = function() {}
			Plane.prototype.fire = function() {
				console.log('发射普通子弹');
			}
			// 添加两个装饰类，分别是导弹与原子弹
			var MissleDecorator = function(plane) {
				this.plane = plane;
			}
			MissleDecorator.prototype.fire = function() {
				this.plane.fire();
				console.log('发射导弹')
			}
			var AtomDecorator = function(plane) {
				this.plane = plane;
			}
			AtomDecorator.prototype.fire = function() {
				this.plane.fire();
				console.log('发射原子弹')
			}
			var plane = new Plane();
			// plane.fire(); 		// 发射普通子弹
			plane = new MissleDecorator(plane);
			// plane.fire(); 		// 发射普通子弹  发射导弹
			plane = new AtomDecorator(plane);
			// plane.fire();   	// 发射普通子弹  发射导弹 发射原子弹*/

			// 方案一改造：
			/*var plane = {
				fire: function() {
					console.log('发射普通子弹')
				}
			}

			var missleDecorator = function() {
				console.log('发射导弹')
			}

			var atomDecorator = function() {
				console.log('发射原子弹');
			}

			var fire1 = plane.fire;
			plane.fire = function() {
				missleDecorator();
				fire1();
			}

			// plane.fire(); // 发射导弹  发射普通子弹

			var fire2 = plane.fire;
			plane.fire = function() {
				atomDecorator();
				fire2();
			}

			plane.fire();  // 发射原子弹  发射导弹  发射普通子弹*/

			// 函数装饰
			
			/*var a = function() {
				console.log('a')
			}

			// 直接改写
			// a = function() {
			// 	console.log('a');
			// 	console.log('b');
			// }
			// a();

			// 保存引用
			var _a  = a;
			a = function() {
				_a();
				console.log('b')
			}
			a();

			window.onload = function() {
				console.log('load');
			}

			var _onload = window.onload || function() {}
			window.onload = function() {
				_onload();
				console.log('load new')
			}*/

			// 保存引用的方式存在许多缺陷
			// 1：随着装饰者的添加中间维护的内容越来越多 如:_load ...
			// 2：导致函数的指向丢失，如下
			// var _getElementById = document.getElementById;
			// document.getElementById = function(id) {
			// 	console.log('get element by id ...');
			// 	//return _getElementById(id) // 这里_getElementById函数的this执行window
			// 	return _getElementById.call(document, id) // 修改
			// }
			// var box = document.getElementById('content-box-1')
			// console.log(box)

			// 用AOP修饰函数
			/*// 实现before after的函数原型的方法
			Function.prototype.before = function(beforeFn) {
				var _this = this; // this执行before的调用者  也就是函数的本生
				return function() {
					beforeFn.apply(this, arguments);  // 这里的this指向执行时的语法环境
					return _this.apply(this, arguments)// 这里的this指向执行时的语法环境
				}
			}

			Function.prototype.after = function(afterFn) {
				var _this = this; // this执行before的调用者  也就是函数的本生
				return function() {
					var ret = _this.apply(this, arguments);// 这里的this指向执行时的语法环境
					afterFn.apply(this, arguments);// 这里的this指向执行时的语法环境
					return ret;
				}
			}

			// 如果不愿改变原型方法可以使用函数代替
			var generatorAfter = function(fn, afterFn) {
				return function() {
					var ret = fn.apply(this, arguments);
					afterFn.apply(this, arguments);
					return ret;
				}
			}

			var generatorBefore = function(fn, beforeFn) {
				return function() {
					beforeFn.apply(this, arguments);
					return fn.apply(this, arguments);
				}
			}

			// document.getElementById = document.getElementById.before(function() {
			// 	console.log(this);
			// })

			document.getElementById = generatorBefore(document.getElementById, function() {
				console.log(this)
			})

			// document.getElementById为一个函数，使用before修饰函数，返回一个函数
			// 返回的函数被重新赋值给document对象的getElementById属性
			// document.getElementById.before(function() {
			//	console.log(this);
			// })
			// 此时返回函数的this指向getElementById函数的调用者document
			var btn = document.getElementById('content-box-1');
			// console.log(btn)  // 

			// 使用装饰函数after改写window.onload
			window.onload = function initload() { console.log(1) }
			window.onload = (window.onload || function() {})
												.after(function afn1() { console.log(2) })
												// 此处after为window.onload的装饰函数，返回一个包含afn1函数的代理函数 backfn1 = function() {initload.apply(this, arguments); afn1.apply(this, arguments)()}
												.after(function afn2() { console.log(3) })
												// 此处after为backfn1的装饰函数，返回一个包含afn2函数的代理函数 backfn2 = function() {backfn1.apply(this, arguments);afn2.apply(this, arguments)}
												.after(function afn3() { console.log(4) })
												// 此处after为backfn2的装饰函数，返回一个包含afn3函数的代理函数 backfn3 = function() {backfn2.apply(this, arguments);afn3.apply(this, arguments)}*/

			// 数据统计的实现
			/*var showLogin = function() {
				console.log('打开登录弹框');
				log(this.getAttribute('tag'));
			}

			var log = function(tag) {
				console.log('上报标签：' + tag);
			 (new Image).src = 'https://...'; // 上报统计
			}
			document.getElementById('login-btn').onclick = showLogin;

			// 使用AOP来实现
			Function.prototype.after = function(afterFn) {
				var _seft = this;
				return function() {
					var ret = _seft.apply(this, arguments);
					afterFn.apply(this, arguments);
					return ret
				}
			}
			var AopShowLogin = function() {
				console.log('打开登录弹框');
			}

			var AopLog = function() {
				console.log('上报标签：' + this.getAttribute('tag'));
			}

			document.getElementById('login-btn-2').onclick = AopShowLogin.after(AopLog);*/

			// 使用AOP修改函数的参数
			/*Function.prototype.after = function(afterFn) {
				var _self = this;
				return function() {
					var ret = _self.apply(this, arguments);
					afterFn.apply(this, arguments);
					return ret;
				}
			}

			Function.prototype.before = function(beforeFn) {
				var _self = this;
				return function() {
					beforeFn.apply(this, arguments);
					return _self.apply(this, arguments);
				}
			}

			var fnuc = function(params) {
				console.log(params);
			};

			var fnucBe = function(params) {
				params.b = 'b'
			};

			var ffnuc = fnuc.before(fnucBe);

			ffnuc({a: '1'}); // { a: "1", b: "b" }

			// 改变ajax的参数，添加token
			var ajax = function(url, type, params) {
				console.log(params)
			}
			var getToken = function() {
				return 'Token'
			}
			var decoratorAjax = ajax.before(function(url, type, params) {
				params.token = getToken();
			})

			decoratorAjax('https://www.baidu.com', 'jsonp', {_n: '百度'});*/
		})()
	</script>
</body>
</html>